package cz.data.domain.market.mapping.handler.interceptor;

import cz.data.common.annotation.DataInner;
import cz.data.common.base.SimpleDataResponse;
import cz.data.common.core.AuthUser;
import cz.data.common.core.BasicToken;
import cz.data.common.core.DataConstant;
import cz.data.common.core.DataUser;
import cz.data.common.exception.AuthException;
import cz.data.common.redis.service.RedisService;
import cz.data.common.security.utils.SecurityUtils;
import cz.data.common.utils.SecurityUtil;
import cz.data.common.utils.ThrowableUtil;
import cz.data.domain.security.rpc.OAuth2ServiceFeign;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;

public abstract class AbstractRequestInterceptor {
    private static final String CACHE_PREFIX = DataConstant.Token.JTI_CACHE_PREFIX;
    @Resource
    OAuth2ServiceFeign oAuth2ServiceFeign;
    @Resource
    RedisService redisService;
    @Resource
    JwtDecoder jwtDecoder;

    public void authorization(HttpServletRequest request, String access) throws AuthException  {
        Object principal;
        if (DataInner.Access.authenticated.getValue().equals(access)) {
            String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
            Object userinfo = userinfo(authorization);
            if (Objects.isNull(userinfo))
                throw new AuthException("授权失败!客户端凭证错误");
            principal = userinfo;
        } else if (DataInner.Access.permitAll.getValue().equals(access)) {
            principal = AuthUser.from(access);
        } else if (DataInner.Access.anonymous.getValue().equals(access)) {
            principal = AuthUser.from(access);
        } else {
            throw new AuthException("授权失败!API接口不允许访问");
        }
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(principal, principal, Collections.emptyList());
        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }

    private Object userinfo(String authorization) {
        if (StringUtils.isBlank(authorization))
            return null;
        Jwt jwt = ThrowableUtil.callable(() -> jwtDecoder.decode(SecurityUtils.removeTokenType(authorization)), false);
        if (Objects.nonNull(jwt)) {
            String jti = jwt.getId();
            Object value = redisService.get(CACHE_PREFIX + jti);
            if (Objects.nonNull(value))
                return value;
            SimpleDataResponse<DataUser> response = oAuth2ServiceFeign.userinfo(authorization);
            DataUser dataUser = Optional.ofNullable(response).map(SimpleDataResponse::getData).orElse(null);
            if (Objects.nonNull(dataUser)) {
                redisService.set(CACHE_PREFIX + jti, dataUser, 600L);
            }
            return dataUser;
        } else {
            BasicToken basicToken = SecurityUtil.converterBasicToken(authorization);
            String id = basicToken.getUsername() + ":" + basicToken.getPassword();
            Object value = redisService.get(CACHE_PREFIX + id);
            if (Objects.nonNull(value))
                return value;
            SimpleDataResponse<AuthUser> response = oAuth2ServiceFeign.authorizeUserinfo(authorization);
            AuthUser user = Optional.ofNullable(response).map(SimpleDataResponse::getData).orElse(null);
            if (Objects.nonNull(user)) {
                redisService.set(CACHE_PREFIX + id, user, 600L);
            }
            return user;
        }
    }
}
